package com.yvan.cs;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Joiner;
import com.yvan.cs.platform.JsonWapper;
import com.yvan.cs.platform.YvanUtil;
import com.yvan.cs.process.ActionProcess;
import com.yvan.cs.process.JavaProcess;
import com.yvan.cs.process.TableProcess;
import com.yvan.cs.utils.FileUtils;
import com.yvan.cs.utils.Settings;
import com.yvan.cs.utils.XmlUtils;
import com.yvan.cs.utils.util;
import javafx.util.Pair;
import lombok.SneakyThrows;
import lombok.val;
import org.apache.commons.io.IOUtils;
import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.resource.FileResourceLoader;
import org.beetl.core.resource.StringTemplateResourceLoader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;
import java.util.Map;

class CodeSlave {

    private static final String XSI_TABLE = "table";
    private static final String XSI_FILE_GENERATOR = "fileGenerator";
    private static final String XSI_JAVA_GENERATOR = "javaGenerator";

    static volatile CodeSlave INSTANCE = new CodeSlave();
    Settings settings;
    private Map<String, TemplateSetting> templateCache;
    private Map<String, Object> globalProperty;
    private final GroupTemplate groupTemplate;

    @SneakyThrows
    private CodeSlave() {
        StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader();
        Configuration cfg = Configuration.defaultConfiguration();
        groupTemplate = new GroupTemplate(resourceLoader, cfg);
        groupTemplate.registerFunctionPackage("util", util.class);
    }

    void reload() {
        if (settings == null) {
            throw new RuntimeException("settings 不能为空" );
        }

        settings.reload();
        globalProperty = settings.getJw().asMap("global" );

        templateCache = settings.getTemplates();
        for (TemplateSetting templateSetting : templateCache.values()) {
            templateSetting.init(settings);
        }
    }

    /**
     * 查看 Model 最终效果
     *
     * @param filePath module文件路径
     */
    @SneakyThrows
    Pair<JsonWapper, TemplateSetting> processModule(String filePath) {
        //检查文件和设置
        if (settings == null) {
            throw new RuntimeException("settings 不能为空" );
        }

        File file = new File(filePath);
        if (!file.exists() || !file.isFile()) {
            throw new RuntimeException("非法路径:" + filePath);
        }

        //找到模版
        TemplateSetting templateSetting;
        JsonNode moduleNode = XmlUtils.readXmlToNode(file);
        try {
            String templateName = moduleNode.get("template" ).asText();
            templateSetting = templateCache.get(templateName);
            if (templateSetting == null) {
                throw new RuntimeException("没有定义模版:" + templateName);
            }
        } catch (Exception e) {
            throw new RuntimeException("没有定义 template 属性:" + filePath, e);
        }

        //将模版中的基类文件与当前模块合并
        JsonNode node = templateSetting.getJsonNode().deepCopy();
        YvanUtil.merge(node, moduleNode);

        //替换变量，并生成 Module 文件
        JsonWapper jw = randerTemplate(node);

        //找到所有 Table，并重新定义 Columns 的属性
        //处理表格
        for (val table : util.tableNames(jw)) {
            TableProcess.process(jw.asDic(table));
        }

        //定义 actions
        //TableProcess.processActions(jw);
        ActionProcess.process(jw);

        //处理 JavaFile
        JavaProcess.process(jw);
        return new Pair<>(jw, templateSetting);
    }

    /**
     * 执行代码生成
     *
     * @param filePath module文件路径
     */
    @SneakyThrows
    void run(String filePath) {
        val kv = processModule(filePath);
        JsonWapper jw = kv.getKey();
        TemplateSetting templateSetting = kv.getValue();

        FileResourceLoader resourceLoader = new FileResourceLoader();
        resourceLoader.setAutoCheck(true);
        resourceLoader.setCharset("UTF-8" );
        resourceLoader.setRoot(
                FileUtils.normalizePath(settings.getTemplateBase())
        );

        Configuration cfg = Configuration.defaultConfiguration();
        val templateEngin = new GroupTemplate(resourceLoader, cfg);
        templateEngin.registerFunctionPackage("util", util.class);

        for (val fileKey : jw.keys(Consts.File)) {
            val jwFile = jw.asDic(Consts.File, fileKey);
            if (jwFile.asBoolean(Consts.JAVA_DISABLE)) {
                continue;
            }
            templateFileProcess(templateEngin, templateSetting, fileKey, jw, jwFile, jw.asDic(jwFile.asStr("forTable" )));
            //System.out.println(jwFile.toString(true));
        }

        /*
        //扫描 Module 文件中 type = javaGenerator / fileGenerator 的所有节点
        // 找到 fileName+basePath 或 namespace+basePath 并且 templateSetting 属性不为空的所有属性
        List<GeneratorParam> generatorParamList = findGeneratorParams(jw);

        //根据文件路径，执行模版渲染，并生成代码文件
        for (GeneratorParam param : generatorParamList) {

            File templateFile = FileUtils.getResource(settings.getTemplateBase(), templateSetting.getTemplateBasePath(), param.getTemplateFile());
            File targetFile = FileUtils.getResource(param.getTargetFile());

            try {
                File parent = targetFile.getParentFile();
                if (!parent.exists()) {
                    parent.mkdirs();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            Template t = groupTemplate.getTemplate(templateFileProcess(templateFile));
            t.binding("_root", globalProperty);
            t.binding("Module", jw.getInnerMap());
            System.out.println("render_to:" + targetFile.getCanonicalPath());
            try (FileOutputStream outputStream = new FileOutputStream(targetFile)) {
                t.renderTo(outputStream);
            }
        }
        */
    }

    @SneakyThrows
    private void templateFileProcess(GroupTemplate templateEngin,
                                     TemplateSetting templateSetting,
                                     String targetFilePath,
                                     JsonWapper module,
                                     JsonWapper jwFile,
                                     JsonWapper jwTable) {


        File targetFile = FileUtils.getResource(targetFilePath);

        Template t = templateEngin.getTemplate(
                FileUtils.normalizePath(templateSetting.getTemplateBasePath(), jwFile.asStr("template" ))
        );

        try {
            File parent = targetFile.getParentFile();
            if (!parent.exists()) {
                parent.mkdirs();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


        t.binding("_root", globalProperty);
        t.binding(module.getInnerMap());
        t.binding("myTable", jwTable.getInnerMap());
        t.binding("myFile", jwFile.getInnerMap());
        t.binding("this", module.getInnerMap());
        System.out.println("render_to:" + targetFile.getCanonicalPath());
        try (FileOutputStream outputStream = new FileOutputStream(targetFile)) {
            t.renderTo(outputStream);
        }
    }

    @SneakyThrows
    private String templateFileProcess(File templateFile) {
        try (FileInputStream fis = new FileInputStream(templateFile)) {
            List<String> lines = IOUtils.readLines(fis, "UTF-8" );
            if (lines.size() > 0 && lines.get(0).contains("@formatter:" )) {
                return Joiner.on("\r\n" ).join(lines.subList(1, lines.size()));
            }
            return Joiner.on("\r\n" ).join(lines);
        }
    }

    @SneakyThrows
    private JsonWapper randerTemplate(JsonNode node) {
        JsonWapper jw = new JsonWapper(node);
        Template template = groupTemplate.getTemplate(jw.toString(true));
        template.binding("_root", globalProperty);
        template.binding(jw.getInnerMap());
        String renderResult = template.render();
        while (renderResult.contains("${" ) || renderResult.contains("<%" )) {
            template = groupTemplate.getTemplate(renderResult);
            template.binding("_root", globalProperty);
            template.binding(new JsonWapper(renderResult).getInnerMap());
            renderResult = template.render();
        }
        return new JsonWapper(renderResult);
    }
}
